package cool.taomu.toolkit.kademlia.service

import com.google.common.base.Function
import cool.taomu.toolkit.kademlia.entity.KademliaNode
import cool.taomu.toolkit.kademlia.utils.KademliaUtils
import java.math.BigInteger
import java.util.ArrayList
import java.util.List
import java.util.concurrent.ArrayBlockingQueue
import org.eclipse.xtend.lib.annotations.Accessors
import org.slf4j.LoggerFactory

@Accessors
class RoutingTable {
	val KademliaNode localNode; // 当前节点
	val List<ArrayBlockingQueue<KademliaNode>> buckets; // 路由表桶
	val LOG = LoggerFactory.getLogger(RoutingTable);
	var int bucketsTotalSize = 0;
	int k = 8;

	new(KademliaNode localNode, int k) {
		this.localNode = localNode;
		this.k = k;
		this.buckets = new ArrayList(160); // 假设节点ID为160位
		for (var int i = 0; i < 160; i++) {
			buckets.add(new ArrayBlockingQueue<KademliaNode>(k));
		}
	}

	def static nodesFindByHostAndPort(ArrayBlockingQueue<KademliaNode> nodes, KademliaNode node) {
		return nodes.findFirst[it.host == node.host && it.port == node.port]
	}

	// 添加节点到路由表
	def void addNode(KademliaNode node, Function<KademliaNode, Boolean> checkRemoteNode) {
		if (node.equals(this.localNode)) {
			return;
		}
		// 获取节点所属的桶索引
		var int bucketIndex = getBucketIndex(localNode.getId(), node.getId());
		// 获取桶索引对应的队列
		val queue = buckets.get(bucketIndex);
		// 根据node中的host和port查找队列中是否存在这个节点
		val bucketsNode = queue.nodesFindByHostAndPort(node);
		// 为 null 是不存在这个节点
		if (bucketsNode === null) {
			if (queue.size < this.k) {
				LOG.info("节点{}不存在K桶中，将其加入k桶", node);
				node.createTimestemp = System.currentTimeMillis
				node.timestamp = node.createTimestemp
				if (queue.add(node)) {
					LOG.info("加入k桶成功");
				}
				checkRemoteNode.apply(node);
				LOG.info("k桶大小:{}", queue.size);
			} else {
				val knode = queue.take();
				LOG.info("对节点{}进行ping判断", node);
				try {
					if (checkRemoteNode.apply(knode)) {
						LOG.info("节点{}成功返回PONG,将其加入k桶队列末尾", knode);
						knode.timestamp = System.currentTimeMillis;
						queue.add(knode);
					} else if (checkRemoteNode.apply(knode)) {
						LOG.info("将新节点{}加入k桶成功", node);
						node.createTimestemp = System.currentTimeMillis
						node.timestamp = node.createTimestemp
						queue.add(node);
					} else {
						LOG.info("将新节点{}加入k桶失败", node);
					}
				} catch (Exception ex) {
					ex.printStackTrace
					node.createTimestemp = System.currentTimeMillis
					node.timestamp = node.createTimestemp
					if (checkRemoteNode.apply(knode) && queue.add(node)) {
						LOG.info("将新节点{}加入k桶成功", node);
					} else {
						LOG.info("将新节点{}加入k桶失败", node);
					}
				}
			}
		} else {
			LOG.info("节点{}存在K桶中，将其移动到末尾", node);
			var temp = queue.toList;
			temp.remove(bucketsNode);
			queue.clear;
			queue.addAll(temp);
			if (bucketsNode.id.equals(node.id)) {
				LOG.info("k桶中的节点id相同");
				queue.add(bucketsNode);
			} else if (checkRemoteNode.apply(node)) {
				LOG.info("k桶中的节点id不相同,添加新节点");
				node.timestamp = System.currentTimeMillis
				queue.add(node);
			} else {
				LOG.info("将新节点{}加入k桶失败", node);
			}
		}
		this.bucketsTotalSize = buckets.stream().mapToInt[ it.size ].sum();
	}

	// 查找距离目标ID最近的k个节点
	def List<KademliaNode> findClosestNodes(String targetId, int k) {
		LOG.info("查找距离目标ID:{} 最近的{}个节点", targetId, k);
		var closestNodes = new ArrayList<KademliaNode>();
		for (var int i = 0; i < buckets.size && closestNodes.size < k; i++) {
			if (!buckets.get(i).empty) {
				LOG.info("找到节点");
				closestNodes.addAll(buckets.get(i));
			}
		}
		LOG.info("找到节点数据:{}", closestNodes.size);
		closestNodes.sort([ n1, n2 |
			xorDistance(targetId, n1.getId()).compareTo(xorDistance(targetId, n2.getId()))
		]);
		return closestNodes.subList(0, Math.min(k, closestNodes.size()));
	}

	// 计算两个节点之间的XOR距离
	//def static BigInteger xorDistance(String id1, String id2) {
	//	KademliaUtils.xorDistance(id1, id2)
	//}

	// 获取节点所属的桶索引
	def int getBucketIndex(String id1, String id2) {
		return xorDistance(id1, id2).bitCount;
	}
	
		// 计算两个节点之间的XOR距离
	def static BigInteger xorDistance(String id1, String id2) {
		//增加缓存
		var BigInteger bigInt1 = new BigInteger(id1, 2);
		var BigInteger bigInt2 = new BigInteger(id2, 2);
		return bigInt1.xor(bigInt2);
	}
}
